package com.abel.bigwater.kmgw

import com.abel.bigwater.dataserver.model.ServiceConfig
import com.abel.bigwater.kmgw.dao.ConfigDao
import com.abel.bigwater.kmgw.dao.FlowDao
import com.abel.bigwater.kmgw.dao.RtuDao
import com.abel.bigwater.mapper.DataMapper
import com.abel.bigwater.mapper.MeterMapper
import com.abel.bigwater.mapper.RtuMapper
import io.netty.bootstrap.Bootstrap
import io.netty.bootstrap.ServerBootstrap
import io.netty.channel.ChannelInitializer
import io.netty.channel.ChannelOption
import io.netty.channel.nio.NioEventLoopGroup
import io.netty.channel.socket.SocketChannel
import io.netty.channel.socket.nio.NioDatagramChannel
import io.netty.channel.socket.nio.NioServerSocketChannel
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service


/**
 * protocol of Kmcge
 */
@Service
class QdiesServer {

    @Autowired
    var cfgDao: ConfigDao? = null

    @Autowired
    var flowDao: FlowDao? = null

    @Autowired
    var rtuDao: RtuDao? = null

    @Autowired
    var dataMapper: DataMapper? = null

    @Autowired
    var meterMapper: MeterMapper? = null

    @Autowired
    var rtuMapper: RtuMapper? = null

    var cfg: ServiceConfig? = null

    var bossGroup: NioEventLoopGroup? = null
    var workerGroup: NioEventLoopGroup? = null

    /**
     * start in a thread.
     */
    fun startService() {
        cfg = cfgDao?.getConfig(QDIES_SERVER_CFG_ID)
        cfg!!.running = true

        val port = cfg?.localPort ?: QDIES_PORT

        Thread {
            startQdiesServer(port)
        }.also {
            it.isDaemon = true
            it.start()
            it.join(100)
        }

        Thread {
            launcUdpServer(port)
        }.also {
            it.isDaemon = true
            it.start()
            it.join(100)
        }
    }

    /**
     * stop the serice.
     */
    fun stopService() {
        workerGroup?.shutdownGracefully()
        bossGroup?.shutdownGracefully()
        cfg?.running = false

        lgr.info("$QDIES_SERVER_NAME 已退出.")
    }

    protected fun startQdiesServer(port: Int) {
        cfg = cfgDao?.getConfig(QDIES_SERVER_CFG_ID)
        cfg!!.running = true

        lgr.info("$QDIES_SERVER_NAME 启动中...(端口: $port)")
        try {
            /**
             * ServerBootstrap 是一个启动NIO服务的辅助启动类 你可以在这个服务中直接使用Channel
             */
            var b = ServerBootstrap()
            bossGroup = NioEventLoopGroup()
            workerGroup = NioEventLoopGroup()

            /**
             * 这一步是必须的，如果没有设置group将会报java.lang.IllegalStateException: group not
             * set异常
             */
            b = b.group(bossGroup, workerGroup)

            /***
             * ServerSocketChannel以NIO的selector为基础进行实现的，用来接收新的连接
             * 这里告诉Channel如何获取新的连接.
             */
            b = b.channel(NioServerSocketChannel::class.java)

            /***
             * 这里的事件处理类经常会被用来处理一个最近的已经接收的Channel。 ChannelInitializer是一个特殊的处理类，
             * 他的目的是帮助使用者配置一个新的Channel。
             * 也许你想通过增加一些处理类比如NettyServerHandler来配置一个新的Channel
             * 或者其对应的ChannelPipeline来实现你的网络程序。 当你的程序变的复杂时，可能你会增加更多的处理类到pipline上，
             * 然后提取这些匿名类到最顶层的类上。
             */
            b = b.childHandler(object : ChannelInitializer<SocketChannel>() { // (4)
                @Throws(Exception::class)
                public override fun initChannel(ch: SocketChannel) {
                    ch.pipeline().addLast(QdiesDecoder(),
                            QdiesServerHandler(dataMapper, meterMapper, rtuMapper, rtuDao, cfg!!, flowDao!!, PACKET_COL))
                }
            })

            /***
             * 你可以设置这里指定的通道实现的配置参数。 我们正在写一个TCP/IP的服务端，
             * 因此我们被允许设置socket的参数选项比如tcpNoDelay和keepAlive。
             * 请参考ChannelOption和详细的ChannelConfig实现的接口文档以此可以对ChannelOptions的有一个大概的认识。
             */
            b = b.option(ChannelOption.SO_BACKLOG, 128)

            /***
             * option()是提供给NioServerSocketChannel用来接收进来的连接。
             * childOption()是提供给由父管道ServerChannel接收到的连接，
             * 在这个例子中也是NioServerSocketChannel。
             */
            b = b.childOption(ChannelOption.SO_KEEPALIVE, true)

            /***
             * 绑定端口并启动去接收进来的连接
             */
            val f = b.bind(port).sync()

            /**
             * 这里会一直等待，直到socket被关闭
             */
            f.channel().closeFuture().sync()
        } finally {
            /***
             * 关闭
             */
            workerGroup?.shutdownGracefully()
            bossGroup?.shutdownGracefully()

            lgr.info("$QDIES_SERVER_NAME 已退出.")
        }
    }

    @Throws(Exception::class)
    private fun launcUdpServer(port: Int) {
        val b = Bootstrap()
        val group = NioEventLoopGroup()
        b.group(group)
                .channel(NioDatagramChannel::class.java!!)
                .option(ChannelOption.SO_BROADCAST, true)
                .handler(QdiesServerUdpHandler(dataMapper, meterMapper, rtuMapper, rtuDao, cfg!!, flowDao!!, PACKET_COL))

        lgr.info("${QDIES_SERVER_NAME}启动（UDP端口: ${port}）")
        b.bind(port).sync().channel().closeFuture().await()
    }

    companion object {
        private val lgr = LoggerFactory.getLogger(QdiesServer::class.java)
        const val QDIES_SERVER_NAME = "青岛积成大表服务"

        const val QDIES_PORT = 6436

        const val QDIES_SERVER_CFG_ID = "qdiesServer"

        const val PACKET_COL = "qdies"
    }
}